﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DataAccess = Microsoft.MSE.Catalog.Module.Data;
using DTO = Microsoft.MSE.Catalog.Contracts;
using System.Xml;
using System.Data.SqlClient;
using System.Data;
using Microsoft.MSE;
using System.Xml.XPath;
using System.IO;

namespace Microsoft.MSE.Catalog.Module.Business
{
    public class BindingManager
    {
        public BindingManager(){}


        #region Binding functions

        //A root binding such as wsFederationHttp binding may have multiple bindings inside it
        public static Guid CreateMSEBinding(DTO.MSEBinding bnd)
        {

            //Note: no change tracking, a newly created binding is not yet used by anything

            DTO.MSEBinding binding = bnd; //

            if (bnd.BindingConfig == null && bnd.BindingElements == null)
                throw new ArgumentNullException("Binding configuration is not supplied.");

            if (bnd.BindingConfig != null) // Create Binding from binding xml
            {
                binding.BindingElements = ExtractBindingElementsFromBindingXml(bnd.BindingConfig);
            } //Else Create Bindig from binding elements collection
            else
            {
                CatalogSvcHelper.AssembleBindingFromBindingElements(bnd); //This function throws error if binding is not valid
            }

            using (SqlConnection conn = CatalogSvcHelper.GetConnection())
            {
                conn.Open();
                SqlTransaction txn = CatalogSvcHelper.GetTransaction(conn, IsolationLevel.Snapshot);

                binding.ID = DataAccess.Binding.CreateMSEBinding(binding, txn, conn);

                foreach (DTO.MSEBindingElement bndElement in binding.BindingElements)
                {
                    bool existInCatalog = BindingElementExists(bndElement, txn, conn);

                    if (!existInCatalog)    //Create binding element
                        bndElement.ID = DataAccess.BindingElement.CreateMSEBindingElement(bndElement, txn, conn);

                    //Assign Binding element to Binding
                    AssignBindingElementsToBinding(binding.ID, bndElement.ID, bndElement.ElementOrder, txn, conn);
                }

                if (txn != null) txn.Commit(); //Commit transaction

            } //Connection is disposed here & any transactions rolled back

            return binding.ID;
        }

        public static DTO.MSEBinding GetMSEBinding(Guid ID, SqlTransaction txn, SqlConnection conn)
        {

            return CatalogSvcHelper.AssembleBindingFromBindingElements(DataAccess.Binding.GetMSEBinding(ID, txn, conn));

        }

        public static void DeleteMSEBinding(Guid ID)
        {
            using (SqlConnection conn = CatalogSvcHelper.GetConnection())
            {
                conn.Open();
                SqlTransaction txn = CatalogSvcHelper.GetTransaction(conn, IsolationLevel.Snapshot);

                //Note: No change tracking, cannot delete bindings if they are in use
                try
                {
                    //Get existing binding elements for the binding
                    List<DTO.MSEBindingElement> existingBindingElements = DataAccess.Binding.GetBindingElementsForBindingID(ID, txn, conn);

                    //Delete binding elements first. TODO SProc which deletes all binding elements for a binding ID
                    foreach (DTO.MSEBindingElement bndElement in existingBindingElements)
                    {
                        DataAccess.Binding.RemoveBindingElementsFromBinding(ID, bndElement.ID, bndElement.ElementOrder, txn, conn);
                    }

                    DataAccess.Binding.DeleteMSEBinding(ID, txn, conn);

                    if (txn != null) txn.Commit(); //Commit transaction
                }
                catch (Exception)
                {
                    if (txn != null) txn.Rollback(); //Rollback transaction
                    throw;
                }
            } //Connection is disposed here

        }

        public static DTO.MSEBinding UpdateMSEBinding(DTO.MSEBinding bnd)
        {
            DTO.MSEBinding binding = bnd;

            if (bnd.BindingConfig == null && bnd.BindingElements == null)
                throw new ArgumentNullException("Binding configuration is not supplied.");

            if (bnd.BindingConfig != null) // Create Binding from binding xml
            {
                binding.BindingElements = ExtractBindingElementsFromBindingXml(bnd.BindingConfig);
            }
            else //Else Create Bindig from binding elements collection
            {
                //Validate binding created from Binding Elements
                //This function throws error if binding is not valid
                CatalogSvcHelper.AssembleBindingFromBindingElements(bnd); 
            }

            using (SqlConnection conn = CatalogSvcHelper.GetConnection())
            {
                conn.Open();
                SqlTransaction txn = CatalogSvcHelper.GetTransaction(conn, IsolationLevel.Snapshot);
                
                #region Change Tracking
                DTO.RepositoryChangeSet repositoryChangeSet = ChangeTrackingHelper.GetChangeSetForBindingUpdate(bnd, txn, conn);
                #endregion

                //Calling Data access layer
                DTO.MSEBinding rtrnBinding = DataAccess.Binding.UpdateMSEBinding(binding, txn, conn);

                //Delete binding elements for the binding first.
                List<DTO.MSEBindingElement> existingBindingElements = DataAccess.Binding.GetBindingElementsForBindingID(binding.ID, txn, conn);

                foreach (DTO.MSEBindingElement bndElement in existingBindingElements)
                {
                    DataAccess.Binding.RemoveBindingElementsFromBinding(binding.ID, bndElement.ID, bndElement.ElementOrder, txn, conn);
                }

                foreach (DTO.MSEBindingElement bndElement in binding.BindingElements)
                {
                    bool existInCatalog = BindingElementExists(bndElement, txn, conn);

                    if (!existInCatalog)    //Create binding element
                        bndElement.ID = DataAccess.BindingElement.CreateMSEBindingElement(bndElement, txn, conn);

                    //Assign Binding element to Binding
                    AssignBindingElementsToBinding(binding.ID, bndElement.ID, bndElement.ElementOrder, txn, conn);
                }

                //create repository change record
                DataAccess.RepositoryChange.Save(repositoryChangeSet, txn, conn);

                if (txn != null) txn.Commit(); //Commit transaction

            } //Connection is disposed here & any transactions rolled back

            return binding;

        }

        public static void AssignBindingElementsToBinding(Guid bindingID, Guid bindingElementID, int elementOrder, SqlTransaction txn, SqlConnection conn)
        {
            if (txn == null)
            {
                //if no transaction context we are a top-level call so do change tracking
            }
            DataAccess.Binding.AssignBindingElementsToBinding(bindingID, bindingElementID, elementOrder, txn, conn);
        }

        public static void RemoveBindingElementsFromBinding(Guid bindingID, Guid bindingElementID, int elementOrder, SqlTransaction txn, SqlConnection conn)
        {
            if (txn == null)
            {
                //if no transaction context we are a top-level call so do change tracking
            }
            DataAccess.Binding.RemoveBindingElementsFromBinding(bindingID, bindingElementID, elementOrder, txn, conn);
        }

        public static DTO.MSEBindingList ListMSEBindings( SqlTransaction txn, SqlConnection conn)
        {
            return DataAccess.Binding.ListMSEBindings(txn, conn);
        }

        #endregion

        #region Binding Group functions

        public static List<DTO.MSEBindingListElement> ListMSEBindingsByGroup(Guid entityGroupID)
        {
            return DataAccess.Binding.ListMSEBindingsByGroup(entityGroupID, null, null);

        }

        public static DTO.MSEBindingGroup CreateMSEBindingGroup(DTO.MSEBindingGroup mseBindingGroup)
        {

            using (SqlConnection conn = CatalogSvcHelper.GetConnection())
            {
                conn.Open();
                SqlTransaction txn = CatalogSvcHelper.GetTransaction(conn, IsolationLevel.Snapshot);

                try
                {

                    mseBindingGroup.ID = DataAccess.Entity.CreateEntityGroup(mseBindingGroup.Name, mseBindingGroup.Description, 6, txn, conn);

                    if (mseBindingGroup.MSEBindings != null)
                    {
                        foreach (DTO.MSEBindingListElement mseBinding in mseBindingGroup.MSEBindings)
                        {
                            DataAccess.Entity.AssignEntityToEntityGroup(mseBindingGroup.ID, mseBinding.ID, txn, conn);
                        }
                    }

                    if (txn != null) txn.Commit(); //Commit transaction
                }
                catch (Exception)
                {
                    if (txn != null) txn.Rollback(); //Rollback transaction
                    throw;
                }
            } //Connection is disposed here

            return mseBindingGroup;
        }

        public static DTO.MSEBindingGroup UpdateMSEBindingGroup(DTO.MSEBindingGroup mseBindingGroup)
        {
            using (SqlConnection conn = CatalogSvcHelper.GetConnection())
            {
                conn.Open();
                SqlTransaction txn = CatalogSvcHelper.GetTransaction(conn, IsolationLevel.Snapshot);

                try
                {

                    DataAccess.Entity.UpdateEntityGroup(mseBindingGroup.ID, mseBindingGroup.Name, mseBindingGroup.Description, txn, conn);


                    List<DTO.MSEBindingListElement> oldList = DataAccess.Binding.ListMSEBindingsByGroup(mseBindingGroup.ID, txn, conn);
                    /// Delete all items from the old list
                    if (oldList != null)
                    {
                        foreach (DTO.MSEBindingListElement oldListElement in oldList)
                        {
                            DataAccess.Entity.RemoveEntityFromEntityGroup(mseBindingGroup.ID, oldListElement.ID, txn, conn);
                        }
                    }
                    /// Add all items from the new list
                    if (mseBindingGroup.MSEBindings != null)
                    {
                        foreach (DTO.MSEBindingListElement newListElement in mseBindingGroup.MSEBindings)
                        {
                            DataAccess.Entity.AssignEntityToEntityGroup(mseBindingGroup.ID, newListElement.ID, txn, conn);
                        }
                    }

                    if (txn != null) txn.Commit(); //Commit transaction
                }
                catch (Exception)
                {
                    if (txn != null) txn.Rollback(); //Rollback transaction
                    throw;
                }
            } //Connection is disposed here

            return mseBindingGroup;
        }

        public static DTO.MSEBindingGroup GetMSEBindingGroup(Guid ID)
        {
            DTO.MSEBindingGroup mseBindingGroup = new DTO.MSEBindingGroup();
            DTO.EntityGroup entityGroup = new DTO.EntityGroup();

            entityGroup = DataAccess.Entity.GetEntityGroup(ID, null, null);

            mseBindingGroup.ID = entityGroup.ID;
            mseBindingGroup.Name = entityGroup.Name;
            mseBindingGroup.Description = entityGroup.Description;

            mseBindingGroup.MSEBindings = ListMSEBindingsByGroup(ID);

            return mseBindingGroup;
        }
        #endregion

        #region BindingElement functions

        public static Guid CreateMSEBindingElement(DTO.MSEBindingElement bindingElement, SqlTransaction txn, SqlConnection conn)
        {
            //Note: No change tracking, cannot delete binding elements if they are in use
            return DataAccess.BindingElement.CreateMSEBindingElement(bindingElement, txn, conn);
        }

        public static DTO.MSEBindingElement UpdateMSEBindingElement(DTO.MSEBindingElement bindingElement, SqlTransaction txn, SqlConnection conn)
        {

            #region Change Tracking
            DTO.RepositoryChangeSet repositoryChangeSet = ChangeTrackingHelper.GetChangeSetForBindingElementUpdate(bindingElement, txn, conn);
            #endregion

            DTO.MSEBindingElement rtrnBndEle = DataAccess.BindingElement.UpdateMSEBindingElement(bindingElement, txn, conn);

            //create repository change record
            DataAccess.RepositoryChange.Save(repositoryChangeSet, txn, conn);

            return rtrnBndEle;
        }

        public static void DeleteMSEBindingElement(Guid ID, SqlTransaction txn, SqlConnection conn)
        {
            //Note: No change tracking, cannot delete binding elements if they are in use
            DataAccess.BindingElement.DeleteMSEBindingElement(ID, txn, conn);
        }

        public static DTO.MSEBindingElement GetMSEBindingElement(Guid ID, SqlTransaction txn, SqlConnection conn)
        {
            return DataAccess.BindingElement.GetMSEBindingElement(ID, txn, conn);
        }

        public static DTO.MSEBindingElementList ListMSEBindingElements(SqlTransaction txn, SqlConnection conn)
        {
            return DataAccess.BindingElement.ListMSEBindingElements(txn, conn);
        }
        
        #endregion

        #region private functions

        private static XmlNode RemoveChildNodes(XmlNode node)
        {
            XmlNode nodeCopy = node.Clone();
            int cnt = nodeCopy.ChildNodes.Count;
            for (int j = 0; j < cnt; j++)
            {
                //Remove all child (aka BindingElements) from the source to get the binding element
                nodeCopy.RemoveChild(nodeCopy.ChildNodes[0]);
            }
            return nodeCopy;
        }

        private static List<DTO.MSEBindingElement> ExtractBindingElementsFromBindingXml(XmlElement bindingXml)
        {
            CatalogSvcHelper.VerifyBinding(bindingXml); //Make sure that binding is valid

            if (bindingXml.Name != "bindings")
                throw new ArgumentException("<bindings> node is expected as the root node");

            List<DTO.MSEBindingElement> bindingElements = new List<DTO.MSEBindingElement>();
            XmlElement bindingXmlCopy = bindingXml.Clone() as XmlElement;

            //Get rid of the bindings node
            XmlNodeList rootBindingList = bindingXmlCopy.ChildNodes;

            int i = 0;

            foreach (XmlNode rootBindingNode in rootBindingList)
            {
                //Create the BaseBinding type BE here
                DTO.MSEBindingElement bndRootBele = new DTO.MSEBindingElement();
                bndRootBele.ElementType = DTO.BindingElementType.BaseBinding;
                XmlNode rootNode = RemoveChildNodes(rootBindingNode);
                bndRootBele.BindingElementConfig = rootNode as XmlElement;
                bndRootBele.Name = rootNode.Name;
                bndRootBele.ElementOrder = i;
                bindingElements.Add(bndRootBele);


                //Get the <binding> nodes
                XmlNodeList nodeList = bindingXmlCopy.SelectNodes("//binding");

                //GetMSEBinding binding elements for each binding
                foreach (XmlNode bindingNode in nodeList)
                {
                    i++;

                    //Create the Binding type BE here
                    DTO.MSEBindingElement bndBele = new DTO.MSEBindingElement();
                    bndBele.ElementType = DTO.BindingElementType.Binding;
                    bndBele.BindingElementConfig = RemoveChildNodes(bindingNode) as XmlElement;
                    bndBele.Name = bindingNode.Attributes["name"].Value;
                    bndBele.ElementOrder = i;
                    bindingElements.Add(bndBele);

                    //Create rest of the binding elements
                    if (bindingNode.HasChildNodes)
                    {
                        foreach (XmlNode bindingElementNode in bindingNode.ChildNodes)
                        {
                            i++;
                            DTO.MSEBindingElement bele = new DTO.MSEBindingElement();
                            bele.ElementType = DTO.BindingElementType.BindingElement;
                            bele.BindingElementConfig = bindingElementNode as XmlElement;
                            bele.Name = bindingElementNode.Name;
                            bele.ElementOrder = i;
                            bindingElements.Add(bele);
                        }

                    }
                }

            }

            return bindingElements;
        }

        private static bool BindingElementExists(DTO.MSEBindingElement bndElement, SqlTransaction txn, SqlConnection conn)
        {
            //Get all existing binding elements
            //Need to get it every time to avoid BE name clash in case of multiple binding nodes
            DTO.MSEBindingElementList beleList = DataAccess.BindingElement.ListMSEBindingElements(txn, conn);

            bool existInCatalog = false;
            if (beleList.MSEBindingElements != null)
            {
                //See if the binding element already exists in the catalog DB
                for (int i = 0; i < beleList.MSEBindingElements.Count; i++)
                {
                    //Do the name match. Just take the first part of the name. (not the ~<guid> section)
                    string listBeleName = beleList.MSEBindingElements[i].Name;
                    if (listBeleName.IndexOf("~") > 0)
                        listBeleName = listBeleName.Substring(0, listBeleName.IndexOf("~"));
                    if (string.Compare(listBeleName, bndElement.Name, true) == 0)
                    {
                        //Name is same. Make sure that binding configuration is also the same 
                        List<DTO.MSEBindingElement> dbBeleList = DataAccess.BindingElement.GetMSEBindingElementByName(listBeleName, txn, conn);
                        foreach (DTO.MSEBindingElement dbBele in dbBeleList)
                        {
                            if (CatalogSvcHelper.XMLCompare(dbBele.BindingElementConfig, bndElement.BindingElementConfig) == true)
                            {
                                //Match found. No need to create one.
                                existInCatalog = true;
                                bndElement.ID = dbBele.ID;
                                break;
                            }
                        }
                        if (!existInCatalog)
                            bndElement.Name = bndElement.Name + "~" + Guid.NewGuid();
                    }
                }
            }
            return existInCatalog;
        }

        #endregion
    }
}
